package com.yami.shop.nftaccount.common.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yami.shop.bean.dto.TransferDto;
import com.yami.shop.bean.model.NftTransferRecord;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.util.DataWebService;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.config.ShopConfig;
import com.yami.shop.nftaccount.common.dao.AssetExceptionMapper;
import com.yami.shop.nftaccount.common.dao.NftAssetSettleMapper;
import com.yami.shop.nftaccount.common.dao.NftAssetTrendsMapper;
import com.yami.shop.nftaccount.common.model.AssetException;
import com.yami.shop.nftaccount.common.model.NftAssetSettle;
import com.yami.shop.nftaccount.common.model.NftAssetTrends;
import com.yami.shop.nftaccount.common.service.NftAssetSettleService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@AllArgsConstructor
public class NftAssetSettleServiceImpl extends ServiceImpl<NftAssetSettleMapper, NftAssetSettle> implements NftAssetSettleService {

    private final AssetExceptionMapper assetExceptionMapper;

    private final NftAssetSettleMapper assetSettleMapper;

    private final NftAssetTrendsMapper assetTrendsMapper;

    private final ShopConfig shopConfig;

    private final DataWebService dataWebService;
    
    private static final int SELECT_PAGE_SIZE = 200;
    
    private static final int QUERY_PAGE_SIZE = 20;
    
    private static final String ASSET_SETTLE_KEY = "mallcloud_nftaccount:asset_settle";


    @Override
    public void toBeSettled() {
        List<NftAssetSettle> list = assetSettleMapper.getSettleListExcludeException(SELECT_PAGE_SIZE);
        if (CollUtil.isEmpty(list)) {
            log.info("无到结算时间的资产");
            return;
        }
        for (NftAssetSettle settle : list) {
            List<NftAssetTrends> trendsList = assetTrendsMapper.selectList(new LambdaQueryWrapper<NftAssetTrends>().eq(NftAssetTrends::getSettleId, settle.getId()).orderByAsc(NftAssetTrends::getCreateTime).orderByAsc(NftAssetTrends::getId));
            if (CollUtil.isEmpty(trendsList)) {
                continue;
            }
            Map<Integer, List<NftAssetTrends>> listMap = trendsList.stream().collect(Collectors.groupingBy(NftAssetTrends::getStatus));
            Set<Integer> status = listMap.keySet();
            if (status.contains(3)) {
                log.info("TokenId：{}，结算id：{}，异常", settle.getTokenId(), settle.getId());
                settle.setStatus(4);
                saveSettleException(settle.getTokenId(), settle.getId(), "存在【流向】异常账单：" + listMap.get(3).size() + "个，无法结算", null);
                continue;
            }
            if (status.contains(0)) {
                log.info("TokenId：{}，结算id：{}，对账未完成", settle.getTokenId(), settle.getId());
                continue;
            }
            if (status.contains(2)) {
                log.info("TokenId：{}，结算id：{}，存在异常账单", settle.getTokenId(), settle.getId());
                settle.setStatus(4);
                saveSettleException(settle.getTokenId(), settle.getId(), "存在【对账】异常账单：" + listMap.get(2).size() + "个，无法结算", null);
                continue;
            }
            NftAssetTrends endTrends = trendsList.get(trendsList.size() - 1);
            if (endTrends.getToAddress().equals(settle.getToAddress())) {
                settle.setStatus(1);
                settle.setUpdateTime(new Date());
            } else {
                settle.setStatus(4);
                String msg = "资产【落点】异常，结算信息：" + settle.getToAddress() + "；账单id："+ endTrends.getId() +"，接收地址：" + endTrends.getToAddress();
                saveSettleException(settle.getTokenId(), settle.getId(), msg, null);
            }
        }
        List<NftAssetSettle> settleList = list.stream().filter(s -> s.getStatus() != 0).collect(Collectors.toList());
        updateBatchById(settleList);
        log.info("{}个转入待结算或异常", settleList.size());
    }
    
    private void saveSettleException(String tokenId, Long settleId, String msg, String operationId) {
        AssetException exception = new AssetException();
        exception.setTokenId(tokenId);
        exception.setType(2);
        exception.setAssetId(settleId);
        exception.setErrorMsg(msg);
        exception.setCreateTime(new Date());
        exception.setOperationId(operationId);
        assetExceptionMapper.insert(exception);
    }
    
    
    @Override
    public void delivery() {
        String nowDateStr = DateUtil.today();
        Boolean value = RedisUtil.hasKey(ASSET_SETTLE_KEY);
        if (value) {
            log.info("=====正在资产交割中，忽略此次" + nowDateStr);
            return;
        }
        int num = 0;
        RedisUtil.set(ASSET_SETTLE_KEY, nowDateStr + "周期，开始资产交割……" , 0);
        LambdaQueryWrapper<NftAssetSettle> lambdaQueryWrapper = new LambdaQueryWrapper<NftAssetSettle>()
                .eq(NftAssetSettle::getStatus, 1).apply("TO_DAYS(settle_time) <= TO_DAYS('" + nowDateStr + "')")
                .orderByAsc(NftAssetSettle::getCreateTime).orderByAsc(NftAssetSettle::getId).last("limit " + QUERY_PAGE_SIZE);
        while (true) {
            try {
                List<NftAssetSettle> list = list(lambdaQueryWrapper);
                if (CollUtil.isEmpty(list)) {
                    RedisUtil.del(ASSET_SETTLE_KEY);
                    log.info("=====没有待划转的周期资产转移=====");
                    break;
                }
                num += list.size();

                Map<String, List<NftAssetSettle>> chainListMap = list.stream().collect(Collectors.groupingBy(NftAssetSettle::getChainCode));
                
                for (String chain : chainListMap.keySet()) {

                    List<NftAssetSettle> settleList = chainListMap.get(chain);
                    
                    if (chain.equals("dt") || chain.equals("wc")) {
                        // 大唐链  文昌链-天和
                        Map<String, List<NftAssetSettle>> fromMap = settleList.stream().collect(Collectors.groupingBy(NftAssetSettle::getFromMnemonic));
                        for (String fromMnemonic : fromMap.keySet()) {
                            List<NftTransferRecord> transfers = new ArrayList<>();
                            List<NftAssetSettle> settles = fromMap.get(fromMnemonic);
                            settles.forEach(settle -> {
                                NftTransferRecord transfer = new NftTransferRecord();
                                transfer.setDenomId(settle.getDenomId());
                                transfer.setNftId(settle.getTokenId());
                                transfer.setReci(settle.getToAddress());
                                transfer.setSenderAddress(settle.getFromAddress());
                                transfers.add(transfer);
                            });
                            List<Long> settleIds = settles.stream().map(NftAssetSettle::getId).collect(Collectors.toList());
                            try {
                                String url = shopConfig.getApiGatewayUrl() + "/nft/outapi/api/nftmint/transferNFtMsg";
                                Map<String, Object> paramMap = new HashMap<>();
                                paramMap.put("mnemonic", fromMnemonic);
                                paramMap.put("chain", chain);
                                paramMap.put("nftTransferRecordList", transfers);
                                JSONObject jsonObject = dataWebService.hutoolPostObjWebService(url, paramMap,null,null);
                                if (jsonObject.getInteger("code") == Constant.SUCCESS_CODE) {
                                    update(Wrappers.lambdaUpdate(NftAssetSettle.class).set(NftAssetSettle::getStatus, 5).set(NftAssetSettle::getUpdateTime, new Date()).in(NftAssetSettle::getId, settleIds));
                                } else {
                                    update(Wrappers.lambdaUpdate(NftAssetSettle.class).set(NftAssetSettle::getStatus, 3).set(NftAssetSettle::getUpdateTime, new Date()).in(NftAssetSettle::getId, settleIds));
                                    settles.forEach(s -> saveSettleException(s.getTokenId(), s.getId(), jsonObject.getString("msg"), null));
                                }
                            } catch (Exception e) {
                                log.error(chain + "链资产交割失败：", e);
                                continue;
                            }
                        }
                    } else if (chain.equals("avata") || chain.equals("szavata")) {
                        // 文昌链-天舟  文昌链-神舟
                        settleList.forEach(settle -> {
                            String operationId = "dtc" + IdUtil.simpleUUID();
                            settle.setOperationId(operationId);
                            NftTransferRecord transfer = new NftTransferRecord();
                            transfer.setOperationId(operationId);
                            transfer.setDenomId(settle.getDenomId());
                            transfer.setNftId(settle.getTokenId());
                            transfer.setSenderAddress(settle.getFromAddress());
                            transfer.setReci(settle.getToAddress());
                            List<NftTransferRecord> transfers = Collections.singletonList(transfer);
                            try {
                                String url = shopConfig.getApiGatewayUrl() + "/nft/outapi/api/nftmint/transferNFtMsg";
                                Map<String, Object> paramMap = new HashMap<>();
                                paramMap.put("chain", chain);
                                paramMap.put("nftTransferRecordList", transfers);
                                JSONObject jsonObject = dataWebService.hutoolPostObjWebService(url, paramMap, null, null);
                                if (jsonObject.getInteger("code") == Constant.SUCCESS_CODE) {
                                    settle.setStatus(2);
                                } else {
                                    saveSettleException(settle.getTokenId(), settle.getId(), jsonObject.getString("msg"), operationId);
                                }
                                updateById(settle);
                            } catch (Exception e) {
                                log.error("---{}---链 denomId：{}，tokenId：{}，资产交割失败：{}", chain, settle.getDenomId(), settle.getTokenId(), e);
                                return;
                            }
                        });
                    } else if (chain.equals("hw")) {
                        // 华为链
                        settleList.forEach(settle -> {
                            TransferDto transfer = new TransferDto();
                            transfer.setCollectionIndex(settle.getDenomId());
                            transfer.setNftId(settle.getTokenId());
                            transfer.setOwner(settle.getFromAddress());
                            transfer.setReceiver(settle.getToAddress());
//                            transfer.setCert("");
//                            transfer.setKey("");
                            
                            Map<String, Object> paramMap = new HashMap<>();
                            paramMap.put("chain", chain);
                            paramMap.put("transferDto", transfer);
                            String url = shopConfig.getApiGatewayUrl() + "/nft/outapi/api/nftmint/transferNFtMsg";
                            
                            try {
                                JSONObject jsonObject = dataWebService.hutoolPostObjWebService(url, paramMap, null, null);
                                if (jsonObject.getInteger("code") == Constant.SUCCESS_CODE) {
                                    settle.setStatus(5);
                                } else {
                                    settle.setStatus(3);
                                    saveSettleException(settle.getTokenId(), settle.getId(), jsonObject.getString("msg"), null);
                                }
                                updateById(settle);
                                
                            } catch (Exception e) {
                                log.error("---{}华为链 denomId：{}，tokenId：{}，资产交割失败：{}", chain, settle.getDenomId(), settle.getTokenId(), e);
                                return;
                            }
                        });
                        
                    } else {
                        log.error("未对接{}链的资产交割！", chain);
                    }
                }
                
                RedisUtil.set(ASSET_SETTLE_KEY, nowDateStr + "周期，正在资产交割中，当前周期：" + DateUtil.format(list.get(0).getSettleTime(), DatePattern.NORM_DATE_PATTERN) + "，累计已处理：" + num + "个" , 0);
                
            } catch (Exception e) {
                log.error("周期性转移资产ERROR：", e);
            }
        }
    }


    @Override
    public void verifyTransferResults() {
        List<NftAssetSettle> list = list(Wrappers.lambdaQuery(NftAssetSettle.class).eq(NftAssetSettle::getStatus, 2).in(NftAssetSettle::getChainCode, "avata", "szavata"));
        
        list.forEach(s -> {
            String url = shopConfig.getApiGatewayUrl() + "/nft/outapi/api/nftmint/tx/pureResults";
            Map<String, String> paramMap = new HashMap<>();
            paramMap.put("operationId", s.getOperationId());
            paramMap.put("chainCode", s.getChainCode());
            
            JSONObject jsonObject = dataWebService.hutoolGetWebService(url, paramMap, null, null);
            
            if (jsonObject.getInteger("code") == Constant.SUCCESS_CODE) {
                JSONObject data = jsonObject.getJSONObject("data");
                String status = data.getString("status");
                // 交易状态: 0 处理中； 1 成功； 2 失败； 3 未处理
                if (status.equals("1")) {
                    s.setStatus(5);
                } else if (status.equals("2")) {
                    saveSettleException(s.getTokenId(), s.getId(), data.getString("msg"), s.getOperationId());
                    s.setStatus(3);
                } else {
                    return;
                }
                updateById(s);
            } else {
                log.error("资产转让{}，查询上链交易结果ERROR：{}", s.getOperationId(), jsonObject.getString("msg"));
            }
            
        });
    }
    
    
    
    
    
    
    
}
